Introduction
New York City’s 683,000+ street trees form a critical urban forest. In Council District 3 , root damage and branch hazards pose real risks. This project delivers a $350,000 pilot targeting ‘r nrow(risk_d3)’ high/extreme-risk trees (DPR data) — 1.8% of District 3’s canopy.
Show code
# Add Quarto to your PATH for this session
Sys.setenv (PATH = paste ("/usr/local/bin" , Sys.getenv ("PATH" ), sep = ":" ))
# ALSO add the RStudio-embedded Quarto (just in case)
Sys.setenv (PATH = paste ("/Applications/RStudio.app/Contents/Resources/app/quarto/bin" ,
Sys.getenv ("PATH" ), sep = ":" ))
Data Acquisition
Task 1: NYC Council Districts
Show code
download_districts <- function () {
dir.create ("data/mp03" , showWarnings = FALSE , recursive = TRUE )
file <- "data/mp03/nycc_districts.geojson"
if (! file.exists (file)) {
message ("Downloading official NYC Council Districts (2023–present)..." )
request ("https://data.cityofnewyork.us/api/geospatial/yusd-j4xi?method=export&format=GeoJSON" ) %>%
req_perform (path = file)
message ("Download complete." )
} else {
message ("Using cached: nycc_districts.geojson" )
}
districts_raw <- st_read (file, quiet = TRUE )
districts <- districts_raw %>%
rename_with (~ "cncldist" , .cols = matches ("council|dist|cd" , ignore.case = TRUE )) %>%
rename_with (~ "Shape_Area" , .cols = matches ("shape.*area|area" , ignore.case = TRUE )) %>%
select (cncldist, Shape_Area, geometry) %>%
mutate (
cncldist = as.character (cncldist),
Shape_Area = as.numeric (Shape_Area)
) %>%
filter (! is.na (cncldist)) %>%
st_transform (4326 ) %>%
st_simplify (dTolerance = 10 , preserveTopology = TRUE )
message ("Successfully loaded " , nrow (districts), " council districts" )
return (districts)
}
districts <- download_districts ()
districts_simp <- districts
Task 2: NYC Street Trees (683K+ points)
Show code
download_trees <- function (limit = 50000 ) {
base_url <- "https://data.cityofnewyork.us/resource/uvpi-gqnh.csv"
dir.create ("data/mp03" , showWarnings = FALSE , recursive = TRUE )
total_file <- "data/mp03/total_trees.txt"
if (! file.exists (total_file)) {
message ("Getting total count..." )
total <- request ("https://data.cityofnewyork.us/resource/uvpi-gqnh.geojson" ) %>%
req_url_query (` $select ` = "count(*)" ) %>%
req_perform () %>%
resp_body_json () %>%
pluck ("features" , 1 , "properties" , "count" )
writeLines (as.character (total), total_file)
}
total <- as.integer (readLines (total_file))
message ("Total trees: " , format (total, big.mark = "," ))
chunks <- list ()
offset <- 0
i <- 1
while (offset < total) {
file <- sprintf ("data/mp03/trees_%04d.csv" , i)
if (! file.exists (file)) {
message ("Downloading chunk " , i, " (offset " , offset, ")" )
request (base_url) %>%
req_url_query (` $limit ` = limit, ` $offset ` = offset) %>%
req_perform (path = file)
}
chunk <- read_csv (file, show_col_types = FALSE ) %>%
filter (! is.na (longitude), ! is.na (latitude)) %>%
st_as_sf (coords = c ("longitude" , "latitude" ), crs = 4326 )
chunks[[i]] <- chunk
offset <- offset + limit
i <- i + 1
}
bind_rows (chunks)
}
trees <- download_trees ()
trees_sf <- trees
Data Integration
Show code
trees_districts <- st_join (trees, districts_simp, join = st_within) %>%
mutate (cncldist = .data[["cncldist.y" ]]) %>%
select (- any_of (c ("cncldist.x" , "cncldist.y" ))) %>%
filter (! is.na (cncldist)) %>%
mutate (
cncldist = as.character (cncldist),
borough = case_when (
cncldist %in% 1 : 10 ~ "Manhattan" ,
cncldist %in% 11 : 18 ~ "Bronx" ,
cncldist %in% 19 : 32 ~ "Queens" ,
cncldist %in% 33 : 47 ~ "Brooklyn" ,
cncldist %in% 48 : 51 ~ "Staten Island" ,
TRUE ~ "Unknown"
)
)
prob_cols <- c ("root_stone" ,"root_grate" ,"root_other" ,
"trunk_wire" ,"trnk_light" ,"trnk_other" ,
"brch_light" ,"brch_shoe" ,"brch_other" )
trees_districts <- trees_districts %>%
mutate (
across (all_of (prob_cols), ~ . == "Yes" ),
n_problems = rowSums (across (all_of (prob_cols)), na.rm = TRUE ),
high_risk = n_problems >= 3
)
message ("Successfully joined " , format (nrow (trees_districts), big.mark = "," ), " trees to council districts" )
Task 3: Interactive Map of All Trees
Show code
pal <- colorFactor (c ("green" ,"orange" ,"red" ), domain = c ("Alive" ,"Stump" ,"Dead" ))
leaflet (trees_districts) %>%
addProviderTiles (providers$ CartoDB.Positron) %>%
addCircleMarkers (radius = 2 , color = ~ pal (status), fillOpacity = 0.7 ,
stroke = FALSE , clusterOptions = markerClusterOptions ()) %>%
addPolygons (data = districts_simp, fill = NA , color = "black" , weight = 1 ) %>%
addLegend (pal = pal, values = ~ status, title = "Tree Status" )